<?php
// +----------------------------------------------------------------------
// | 控制器 - ADMIN
// +----------------------------------------------------------------------
declare(strict_types=1);

namespace app\controller\admin;

use app\BaseController;
use app\model\SystemMenusModel;
use app\model\SystemUsersLogsModel;
use app\model\SystemUsersMessagesModel;
use app\model\SystemUsersModel;
use app\service\RefreshTokenService;
use app\service\JwtTokenService;
use app\validate\AccessUsersValidate;
use app\validate\SystemUsersValidate;
use think\exception\ValidateException;
use think\Response;

/**
 * 访问控制
 *
 * Class Access
 * @package app\controller\admin
 */
class Access extends BaseController
{

    /**
     * @OA\Post(
     *   path="/api/admin/access/login",
     *   tags={"访问控制"},
     *   summary="用户登录",
     *   description="用户登录并获取用户信息与Token，设备编号是一个客户端管理的字符串。目前用于刷新token",
     *   @OA\RequestBody(
     *   @OA\MediaType(
     *     mediaType="content-type/json",
     *       @OA\Schema(
     *         @OA\Property(description="帐号", property="passport", type="string", example="admin"),
     *         @OA\Property(description="密码", property="password", type="string", example="admin@123456"),
     *         @OA\Property(description="设备编号", property="deviceId", type="string", example="KcFsYHYSlSx2emwobCGT7hULspqxiUX0"),
     *         required={"passport","password", "deviceId"})
     *     )
     *   ),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="accessToken", type="string"),
     *         @OA\Property(property="refreshToken", type="string"),
     *         @OA\Property(property="expires", type="string", example="2025-01-01 12:00:00")
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function login(): Response
    {
        $query = [
            'passport' => $this->request->post('passport'),
            'password' => $this->request->post('password'),
            'deviceId' => $this->request->post('deviceId')
        ];
        try {
            // 字段验证
            validate(AccessUsersValidate::class)->scene('login')->check($query);
            // 登录操作
            $user = SystemUsersModel::queryOrder()->where('username', $query['passport'])->find();
            if (!$user) {
                abort(200, '账号或密码错误');
            }
            if (!verifyPassword($query['password'], $user->password)) {
                abort(200, '账号或密码错误');
            }
            // 写入用户信息
            $this->request->setUser($user);
            // 返回token
            $refreshToken = RefreshTokenService::geneUserToken($user->id, $query['deviceId']);
            return $this->response([
                'accessToken' => JwtTokenService::issuingToken([
                    'uid' => $user->id,
                ]),
                'refreshToken' => $refreshToken,
                'expires' => JwtTokenService::getTokenExpiresTime()->format('Y-m-d H:i:s')
            ]);
            return $this->response();
        } catch (ValidateException $e) {
            // 验证失败 输出错误信息
            abort(200, $e->getError());
        }
    }

    /**
     * @OA\Post(
     *   path="/api/admin/access/refresh-token",
     *   tags={"访问控制"},
     *   summary="刷新Token",
     *   description="刷新并重新获取新的accessToken和refreshToken，原token在有效期内仍然生效",
     *   @OA\RequestBody(
     *   @OA\MediaType(
     *     mediaType="content-type/json",
     *       @OA\Schema(
     *         @OA\Property(description="用户ID", property="uid", type="int", example=1),
     *         @OA\Property(description="设备编号", property="deviceId", type="string", example="KcFsYHYSlSx2emwobCGT7hULspqxiUX0"),
     *         @OA\Property(description="refreshToken", property="refreshToken", type="string", example=""),
     *         required={"uid","deviceId", "refreshToken"})
     *     )
     *   ),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="accessToken", type="string"),
     *         @OA\Property(property="refreshToken", type="string"),
     *         @OA\Property(property="expires", type="string", example="2025-01-01 12:00:00")
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function refreshToken(): Response
    {
        $query = [
            'uid' => $this->request->post('uid'),
            'deviceId' => $this->request->post('deviceId'),
            'refreshToken' => $this->request->post('refreshToken'),
        ];
        try {
            // 字段验证
            validate(AccessUsersValidate::class)->scene('refresh')->check($query);
            // 获取新的token
            $token = RefreshTokenService::refreshUserToken(
                uid: $query['uid'],
                salt: $query['deviceId'],
                token: $query['refreshToken']
            );
            if (!$token) {
                abort(401, '认证失败！请重新登录');
            }
            return $this->response([
                'accessToken' => JwtTokenService::issuingToken([
                    'uid' => $query['uid'],
                ]),
                'refreshToken' => $token,
                'expires' => JwtTokenService::getTokenExpiresTime()->format('Y-m-d H:i:s')
            ]);
        } catch (ValidateException $e) {
            // 验证失败 输出错误信息
            abort(200, $e->getError());
        }
    }

    /**
     * @OA\Get(
     *   path="/api/admin/access/info",
     *   tags={"访问控制"},
     *   summary="获取用户信息",
     *   description="获取当前已登录用户信息",
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="uid", type="integer", example=1),
     *         @OA\Property(property="username", type="string", example="admin"),
     *         @OA\Property(property="nickname", type="string", example="管理员"),
     *         @OA\Property(property="phone", type="string", example="13700000000"),
     *         @OA\Property(property="email", type="string", example="vas-admin@faker.com"),
     *         @OA\Property(property="roles", type="array", items={@OA\Items(type="string")}, example={"SuperAdmin", "Manager"}),
     *         @OA\Property(property="permissions", type="array", items={@OA\Items(type="string")}, example={"*:*:*"}),
     *         @OA\Property(property="remark", type="string", example="个人备注")
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function getUserInfo(): Response
    {
        $user = $this->request->getUserInfo();
        return $this->response([
            'uid' => $user['id'],
            'username' => $user['username'],
            'nickname' => $user['nickname'],
            'phone' => $user['phone'],
            'email' => $user['email'],
            'roles' => $this->request->getUserRoles(),
            'permissions' => $this->request->getUserPermissions(),
            'remark' => $user['remark'],
        ]);
    }

    /**
     * @OA\Put(
     *   path="/api/admin/access/info",
     *   tags={"访问控制"},
     *   summary="修改用户信息",
     *   description="修改当前已登录用户信息（不包括密码）",
     *   @OA\RequestBody(
     *   @OA\MediaType(
     *     mediaType="content-type/json",
     *       @OA\Schema(
     *         @OA\Property(description="昵称", property="nickname", type="string", example="管理员"),
     *         @OA\Property(description="手机号", property="phone", type="string", example="13800000000"),
     *         @OA\Property(description="邮箱", property="email", type="string", example=""),
     *         @OA\Property(description="备注", property="remark", type="string", example="个人备注改"),
     *         required={"nickname"})
     *     )
     *   ),
     *   security={{"bearerAuth":{}}},
     *   @OA\Response(response=401, description="未携带TOKEN或已过期"),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="uid", type="integer", example=1),
     *         @OA\Property(property="username", type="string", example="admin"),
     *         @OA\Property(property="nickname", type="string", example="管理员"),
     *         @OA\Property(property="phone", type="string", example="13700000000"),
     *         @OA\Property(property="email", type="string", example="vas-admin@faker.com"),
     *         @OA\Property(property="roles", type="array", items={@OA\Items(type="string")}, example={"SuperAdmin", "Manager"}),
     *         @OA\Property(property="permissions", type="array", items={@OA\Items(type="string")}, example={"*:*:*"}),
     *         @OA\Property(property="remark", type="string", example="个人备注")
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function setUserInfo(): Response
    {
        $uid = $this->request->getUserId();
        $update = [
            'nickname' => $this->request->post('nickname'),
            'phone' => $this->request->post('phone'),
            'email' => $this->request->post('email'),
            'remark' => $this->request->post('remark')
        ];
        try {
            // 字段验证
            validate(SystemUsersValidate::class)->scene('self_edit')->check($update);
            // 更新用户信息
            $user = SystemUsersModel::where('id', $uid)->find();
            if (!$user) {
                abort(401, '用户信息读取失败');
            }
            $user->save($update);
            return $this->response([
                'uid' => $user->id,
                'username' => $user->username,
                'nickname' => $user->nickname,
                'phone' => $user->phone,
                'email' => $user->email,
                'roles' => $user->roles,
                'permissions' => $user->permissions,
                'remark' => $user->remark
            ]);
        } catch (ValidateException $e) {
            // 验证失败 输出错误信息
            abort(200, $e->getError());
        }
    }

    /**
     * @OA\Put(
     *   path="/api/admin/access/password",
     *   tags={"访问控制"},
     *   summary="修改用户密码",
     *   description="修改当前已登录用户密码",
     *   @OA\RequestBody(
     *   @OA\MediaType(
     *     mediaType="content-type/json",
     *       @OA\Schema(
     *         @OA\Property(description="原密码", property="oldPassword", type="string", example=""),
     *         @OA\Property(description="新密码", property="newPassword", type="string", example=""),
     *         required={"oldPassword", "newPassword"}
     *       )
     *     )
     *   ),
     *   security={{"bearerAuth":{}}},
     *   @OA\Response(response=401, description="未携带TOKEN或已过期"),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         example=null
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function setUserPassword(): Response
    {
        $uid = $this->request->getUserId();
        $oldPassword = $this->request->post('oldPassword');
        $newPassword = $this->request->post('newPassword');
        try {
            // 字段验证
            validate(SystemUsersValidate::class)->scene('password')->check([
                'password' => $oldPassword
            ]);
            validate(SystemUsersValidate::class)->scene('password')->check([
                'password' => $newPassword
            ]);
            // 更新用户密码
            $user = SystemUsersModel::where('id', $uid)->find();
            if (!$user) {
                abort(401, '用户信息读取失败');
            }
            if (!verifyPassword($oldPassword, $user->password)) {
                abort(200, '原密码错误');
            }
            $user->password = $newPassword;
            $user->save();
            return $this->response();
        } catch (ValidateException $e) {
            // 验证失败 输出错误信息
            abort(200, $e->getError());
        }
    }

    /**
     * @OA\Get(
     *   path="/api/admin/access/routes",
     *   tags={"访问控制"},
     *   summary="获取用户路由",
     *   description="返回当前登录用户可操作的路由",
     *   security={{"bearerAuth":{}}},
     *   @OA\Response(response=401, description="未携带TOKEN或已过期"),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="array",
     *         @OA\Items(ref="#/components/schemas/GetSystemMenus")
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function getAsyncRoutes(): Response
    {
        $permissions = $this->request->getUserPermissions();
        $list = SystemMenusModel::queryOrder()->withSearch(
            ['permissions'],
            ['permissions' => $permissions]
        )->select()->toArray();
        foreach ($list as $key => $item) {
            $list[$key] = array_merge($item, $item['options']);
            unset($list[$key]['options']);
        }
        return $this->response($list);
    }

    /**
     * @OA\Get(
     *   path="/api/admin/access/messages",
     *   tags={"访问控制"},
     *   summary="获取用户消息",
     *   description="轮询获取用户最新系统消息和未读消息数",
     *   @OA\Parameter(
     *     name="limit",
     *     in="query",
     *     description="限制消息数量",
     *     example=10,
     *     @OA\Schema(type="integer")
     *   ),
     *   security={{"bearerAuth":{}}},
     *   @OA\Response(response=401, description="未携带TOKEN或已过期"),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="totalUnread", type="integer", example=2),
     *         @OA\Property(
     *           property="list",
     *           type="array",
     *           @OA\Items(ref="#/components/schemas/SystemUsersMessages")
     *         )
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function queryUserMsg(): Response
    {
        $uid = $this->request->getUserId();
        $limit = $this->request->get('limit', 10, 'intval');
        $unread = SystemUsersMessagesModel::withSearch(
            ['uid', 'isRead'],
            ['uid' => $uid, 'isRead' => false]
        )->count();
        $list = SystemUsersMessagesModel::queryOrder()->withSearch(
            ['uid'],
            ['uid' => $uid]
        )->limit(0, $limit)->select()->toArray();
        return $this->response([
            'totalUnread' => $unread,
            'list' => $list,
        ]);
    }

    /**
     * @OA\Post(
     *   path="/api/admin/access/logs/page",
     *   tags={"访问控制"},
     *   summary="获取用户日志分页列表",
     *   @OA\RequestBody(
     *   @OA\MediaType(
     *     mediaType="content-type/json",
     *       @OA\Schema(
     *         @OA\Property(description="当前页", property="page", type="integer", example="1"),
     *         @OA\Property(description="分页数量", property="pageSize", type="integer", example="10"),
     *         required={"page","pageSize"})
     *     )
     *   ),
     *   security={{"bearerAuth":{}}},
     *   @OA\Response(response=401, description="未携带TOKEN或已过期"),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="total", type="integer", example="100"),
     *         @OA\Property(property="currentPage", type="integer", example="1"),
     *         @OA\Property(property="pageSize", type="integer", example="10"),
     *         @OA\Property(
     *           property="list",
     *           type="array",
     *           @OA\Items(ref="#/components/schemas/MineSystemUsersLogs")
     *         )
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function getUserLogsPageList(): Response
    {
        $uid = $this->request->getUserId();
        $list = SystemUsersLogsModel::queryOrder()->withSearch(
            ['uid'],
            ['uid' => $uid]
        )->paginate([
            'list_rows' => $this->request->post('pageSize', 10),
            'page' => $this->request->post('page', 1),
        ])->toArray();
        return $this->response([
            'list' => $list['data'],
            'total' => $list['total'],
            'currentPage' => $list['current_page'],
            'pageSize' => $list['per_page'],
        ]);
    }
}
